In [1]:
import datetime
import pandas as pd
from pandas_datareader import data, wb
import numpy as np
import Quandl
import matplotlib.pylab as pylab
%matplotlib inline

In [2]:
ibm = Quandl.get("GOOG/NYSE_IBM")

In [3]:
ibm.head()


Out[3]:
Open High Low Close Volume
Date
1981-03-11 0 NaN 15.44 15.56 NaN
1981-03-12 0 NaN 15.59 16.00 NaN
1981-03-13 0 NaN 15.81 15.81 NaN
1981-03-16 0 NaN 15.72 16.16 NaN
1981-03-17 0 NaN 16.03 16.06 NaN

In [6]:
start_date = datetime.datetime(2009,1,1)
end_date = datetime.datetime(2014,1,1)
amzn = data.DataReader("AMZN", "yahoo", start_date, end_date)

In [8]:
amzn.head()


Out[8]:
Open High Low Close Volume Adj Close
Date
2009-01-02 51.349998 54.529999 51.070000 54.360001 7296400 54.360001
2009-01-05 55.730000 55.740002 53.029999 54.060001 9509800 54.060001
2009-01-06 54.549999 58.220001 53.750000 57.360001 11080100 57.360001
2009-01-07 56.290001 56.950001 55.349998 56.200001 7942700 56.200001
2009-01-08 54.990002 57.320000 54.580002 57.160000 6577900 57.160000

In [34]:
amzn['Close'].diff().head()


Out[34]:
Date
2009-01-02         NaN
2009-01-05   -0.300000
2009-01-06    3.300000
2009-01-07   -1.160000
2009-01-08    0.959999
Name: Close, dtype: float64

In [9]:
from abc import ABCMeta, abstractmethod

In [10]:
class Strategy(object):
    __metaclass__ = ABCMeta
    
    def __init__(self, symbol, bars, **kwargs):
        self.symbol = symbol
        self.bars = bars
        self.short_window = kwargs.get('short_window', 40)
        self.long_window = kwargs.get('long_window', 100)

    @abstractmethod
    def generate_signals(self):
        raise NotImplementedError("Should implement generate_signals()!")

In [11]:
class MovingAverageCrossStrategy(Strategy):
    def generate_signals(self):
        # Create DataFrame and initialise signal series to zero
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0
        
        # Create the short/long simple moving averages
        signals['short_mavg'] = pd.rolling_mean(self.bars['Adj Close'], self.short_window, min_periods=1)
        signals['long_mavg'] = pd.rolling_mean(self.bars['Adj Close'], self.long_window, min_periods=1)
        
        # When the short SMA exceeds the long SMA, set the ‘signals’ Series to 1 (else 0)
        signals['signal'][self.short_window:] = np.where(signals['short_mavg'][self.short_window:] >
            signals['long_mavg'][self.short_window:], 1, 0)

        # Take the difference of the signals in order to generate actual trading orders
        signals['positions'] = signals['signal'].diff()
        return signals

In [14]:
mac = MovingAverageCrossStrategy("AMZN", amzn, short_window=40, long_window=100)

In [15]:
signals = mac.generate_signals()


/Users/ncharass/anaconda/lib/python3.5/site-packages/ipykernel/__main__.py:13: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

In [36]:
signals.describe()


Out[36]:
signal short_mavg long_mavg positions
count 1258.000000 1258.000000 1258.000000 1257.000000
mean 0.759141 183.060263 176.341728 0.000796
std 0.427775 73.883331 71.138971 0.101733
min 0.000000 51.974737 51.974737 -1.000000
25% 1.000000 125.269313 126.257525 0.000000
50% 1.000000 184.317874 185.349450 0.000000
75% 1.000000 238.840376 231.550950 0.000000
max 1.000000 378.976751 337.112702 1.000000

In [17]:
signals.plot()


Out[17]:
<matplotlib.axes._subplots.AxesSubplot at 0x10d99f5f8>

In [76]:
signals[signals['positions'] == 1]


Out[76]:
signal short_mavg long_mavg positions
Date
2009-03-03 1 58.019250 57.930000 1
2010-04-14 1 129.862750 129.397300 1
2010-09-10 1 126.204500 125.900300 1
2011-05-06 1 180.281501 180.065000 1
2012-04-05 1 187.975749 187.794799 1
2013-01-08 1 247.328750 246.730801 1
2013-06-17 1 265.352749 265.249300 1

In [81]:
signals['positions'].head()


Out[81]:
Date
2009-01-02   NaN
2009-01-05     0
2009-01-06     0
2009-01-07     0
2009-01-08     0
Name: positions, dtype: float64

In [129]:
sum = (signals['positions'].fillna(0) * amzn['Adj Close']).to_frame().sum(axis=1).cumsum()
sum.tail()


Out[129]:
Date
2013-12-24    180.399998
2013-12-26    180.399998
2013-12-27    180.399998
2013-12-30    180.399998
2013-12-31    180.399998
dtype: float64

In [130]:
signals[signals['positions'] < 0]


Out[130]:
signal short_mavg long_mavg positions
Date
2010-03-02 0 122.837750 123.198400 -1
2010-06-18 0 128.460750 128.771900 -1
2011-03-18 0 175.432000 175.756799 -1
2011-12-07 0 212.459500 212.910100 -1
2012-11-27 0 238.923751 239.050300 -1
2013-05-02 0 262.882750 262.959700 -1

In [18]:
class Portfolio(object):
    @abstractmethod
    def generate_positions(self):
        raise NotImplementedError("Should implement generate_positions")
        
    def backtest_portfolio(self):
        raise NotImplementedError("Should implement backtest_portfolio")

In [174]:
class MarketOnClosePortfolio(Portfolio):
    def __init__(self, symbol, bars, signals, **kwargs):
        self.symbol = symbol
        self.bars = bars
        self.signals = signals
        self.positions = signals['positions']
        self.initial_capital = kwargs.get('initial_capital', 100000)
        
    def generate_positions(self):
        # Generate a pandas DataFrame to store quantity held at any “bar” timeframe
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
        positions[self.symbol] = 100 * self.signals['signal'] # Transact 100 shares on a signal
    
        return positions

    def backtest_portfolio(self):
        # Create a new DataFrame ‘portfolio’ to store the market value of an open position
        # positions = self.generate_positions()
        
        portfolio = self.positions * self.bars['Adj Close'] * 100
        #pos_diff = self.positions.diff()
        return portfolio
        
        # Create a ‘holdings’ Series that totals all open position market values
        # and a ‘cash’ column that stores remaining cash in account
        #portfolio['holdings'] = (self.positions*self.bars['Adj Close']).sum(axis=1)
        #portfolio['cash'] = self.initial_capital - (pos_diff*self.bars['Adj Close']).sum(axis=1).cumsum()
        
        # Sum up the cash and holdings to create full account ‘equity’, then create the percentage returns
        #portfolio['total'] = portfolio['cash'] + portfolio['holdings']
        #portfolio['returns'] = portfolio['total'].pct_change()
        
        #return portfolio

In [175]:
portfolio = MarketOnClosePortfolio("AMZN", amzn, signals, initial_capital=100000.0).backtest_portfolio()

In [179]:
portfolio.plot()


Out[179]:
<matplotlib.axes._subplots.AxesSubplot at 0x111a1f198>

In [164]:
portfolio.sum()


Out[164]:
18039.999799999998

From Stackoverflow.


In [188]:
ticker = 'amzn'
px = data.DataReader(ticker, 'yahoo')

def generate_signals(px):
    signals = pd.DataFrame(index=px.index)
    signals['signal'] = 0
    short_ma = pd.rolling_mean(px['Adj Close'], 40, min_periods=1)
    long_ma = pd.rolling_mean(px['Adj Close'], 100, min_periods=1)
    signals['signal'] = np.where(short_ma > long_ma, 1, 0)
    return signals['signal'].shift(1)  # remember to lag your signals :)


px['Signals'] = generate_signals(px)
px['Daily P&L'] = px['Adj Close'].diff() * px['Signals'] * 100
px['Total P&L'] = px['Daily P&L'].cumsum()
print(px[px['Signals'] > 0])


                  Open        High         Low       Close    Volume  \
Date                                                                   
2010-04-06  131.229996  136.000000  131.179993  135.559998   7950300   
2010-04-07  135.960007  136.080002  133.860001  134.869995   5945400   
2010-04-08  134.710007  141.250000  134.710007  140.960007  12689100   
2010-04-09  140.720001  141.330002  139.070007  140.059998   6011100   
2010-04-12  140.000000  142.910004  139.679993  141.199997   5445300   
2010-04-13  141.229996  141.979996  139.119995  140.160004   4785500   
2010-04-14  140.339996  144.500000  139.199997  144.279999   7900300   
2010-04-15  144.550003  147.089996  144.000000  145.820007   7829600   
2010-04-16  144.880005  147.169998  141.449997  142.169998   8396800   
2010-04-19  142.350006  143.669998  139.130005  142.429993   6022000   
2010-04-20  143.830002  144.639999  142.100006  144.199997   4311700   
2010-04-21  145.169998  149.000000  143.520004  146.429993   7374400   
2010-04-22  147.009995  151.089996  145.880005  150.089996  15165800   
2010-04-23  145.380005  149.089996  142.419998  143.630005  18975200   
2010-04-26  143.199997  147.729996  142.899994  147.110001   9318300   
2010-04-27  145.550003  146.440002  141.110001  142.020004   8639000   
2010-04-28  142.589996  142.750000  138.690002  139.350006   9235300   
2010-04-29  140.089996  142.449997  139.789993  141.729996   6314200   
2010-04-30  141.399994  141.399994  136.910004  137.100006   6113500   
2010-05-03  137.199997  139.440002  136.110001  137.490005   5654600   
2010-05-04  135.619995  135.809998  128.380005  129.830002  12667800   
2010-05-05  128.000000  131.610001  127.550003  130.929993   9458100   
2010-05-06  130.000000  132.330002  120.599998  128.710007  10189000   
2010-05-07  127.970001  131.179993  123.760002  124.980003  11927600   
2010-05-10  129.729996  132.210007  129.259995  131.289993   6806100   
2010-05-11  129.949997  133.080002  128.470001  130.460007   6044100   
2010-05-12  131.410004  134.130005  129.679993  133.869995   5904500   
2010-05-13  133.929993  136.990005  131.000000  131.470001   5940200   
2010-05-14  130.360001  131.000000  126.760002  128.529999   5277800   
2010-05-17  128.240005  129.949997  125.800003  128.910004   5642200   
...                ...         ...         ...         ...       ...   
2015-12-11  651.229980  657.880005  639.619995  640.150024   5423600   
2015-12-14  641.750000  658.590027  635.270020  657.909973   4329700   
2015-12-15  665.030029  671.500000  657.349976  658.640015   4724900   
2015-12-16  663.559998  677.349976  659.320007  675.770020   3926600   
2015-12-17  680.000000  682.500000  670.650024  670.650024   3663500   
2015-12-18  668.650024  676.840027  664.130005  664.140015   6765900   
2015-12-21  668.500000  669.900024  658.929993  664.510010   3197500   
2015-12-22  666.830017  668.489990  659.260010  663.150024   2664000   
2015-12-23  666.500000  666.599976  656.630005  663.700012   2714900   
2015-12-24  663.349976  664.679993  660.599976  662.789978   1091200   
2015-12-28  665.559998  675.500000  665.500000  675.200012   3775800   
2015-12-29  677.979980  696.440002  677.890015  693.969971   5721200   
2015-12-30  691.890015  695.489990  686.380005  689.070007   3514300   
2015-12-31  686.080017  687.750000  675.890015  675.890015   3718200   
2016-01-04  656.289978  657.719971  627.510010  636.989990   9280800   
2016-01-05  646.859985  646.909973  627.650024  633.789978   5809600   
2016-01-06  622.000000  639.789978  620.309998  632.650024   5312200   
2016-01-07  621.799988  630.000000  605.210022  607.940002   7015400   
2016-01-08  619.659973  624.140015  606.000000  607.049988   5490700   
2016-01-11  612.479980  619.849976  598.570007  617.739990   4869200   
2016-01-12  625.250000  625.989990  612.239990  617.890015   4690900   
2016-01-13  620.880005  620.880005  579.159973  581.809998   7600000   
2016-01-14  580.250000  602.250000  569.880005  593.000000   7203000   
2016-01-15  572.239990  584.619995  565.299988  570.179993   7754500   
2016-01-19  577.090027  584.000000  566.450012  574.479980   4782800   
2016-01-20  564.359985  578.450012  547.179993  571.770020   7944700   
2016-01-21  573.580017  588.809998  568.219971  575.020020   4932600   
2016-01-22  588.729980  600.099976  584.109985  596.380005   5101100   
2016-01-25  597.989990  608.500000  594.559998  596.530029   4376700   
2016-01-26  603.450012  604.500000  590.380005  601.250000   3743200   

             Adj Close  Signals  Daily P&L   Total P&L  
Date                                                    
2010-04-06  135.559998        1   406.9993    406.9993  
2010-04-07  134.869995        1   -69.0003    337.9990  
2010-04-08  140.960007        1   609.0012    947.0002  
2010-04-09  140.059998        1   -90.0009    856.9993  
2010-04-12  141.199997        1   113.9999    970.9992  
2010-04-13  140.160004        1  -103.9993    866.9999  
2010-04-14  144.279999        1   411.9995   1278.9994  
2010-04-15  145.820007        1   154.0008   1433.0002  
2010-04-16  142.169998        1  -365.0009   1067.9993  
2010-04-19  142.429993        1    25.9995   1093.9988  
2010-04-20  144.199997        1   177.0004   1270.9992  
2010-04-21  146.429993        1   222.9996   1493.9988  
2010-04-22  150.089996        1   366.0003   1859.9991  
2010-04-23  143.630005        1  -645.9991   1214.0000  
2010-04-26  147.110001        1   347.9996   1561.9996  
2010-04-27  142.020004        1  -508.9997   1052.9999  
2010-04-28  139.350006        1  -266.9998    786.0001  
2010-04-29  141.729996        1   237.9990   1023.9991  
2010-04-30  137.100006        1  -462.9990    561.0001  
2010-05-03  137.490005        1    38.9999    600.0000  
2010-05-04  129.830002        1  -766.0003   -166.0003  
2010-05-05  130.929993        1   109.9991    -56.0012  
2010-05-06  128.710007        1  -221.9986   -277.9998  
2010-05-07  124.980003        1  -373.0004   -651.0002  
2010-05-10  131.289993        1   630.9990    -20.0012  
2010-05-11  130.460007        1   -82.9986   -102.9998  
2010-05-12  133.869995        1   340.9988    237.9990  
2010-05-13  131.470001        1  -239.9994     -2.0004  
2010-05-14  128.529999        1  -294.0002   -296.0006  
2010-05-17  128.910004        1    38.0005   -258.0001  
...                ...      ...        ...         ...  
2015-12-11  640.150024        1 -2216.9983  35407.0007  
2015-12-14  657.909973        1  1775.9949  37182.9956  
2015-12-15  658.640015        1    73.0042  37255.9998  
2015-12-16  675.770020        1  1713.0005  38969.0003  
2015-12-17  670.650024        1  -511.9996  38457.0007  
2015-12-18  664.140015        1  -651.0009  37805.9998  
2015-12-21  664.510010        1    36.9995  37842.9993  
2015-12-22  663.150024        1  -135.9986  37707.0007  
2015-12-23  663.700012        1    54.9988  37761.9995  
2015-12-24  662.789978        1   -91.0034  37670.9961  
2015-12-28  675.200012        1  1241.0034  38911.9995  
2015-12-29  693.969971        1  1876.9959  40788.9954  
2015-12-30  689.070007        1  -489.9964  40298.9990  
2015-12-31  675.890015        1 -1317.9992  38980.9998  
2016-01-04  636.989990        1 -3890.0025  35090.9973  
2016-01-05  633.789978        1  -320.0012  34770.9961  
2016-01-06  632.650024        1  -113.9954  34657.0007  
2016-01-07  607.940002        1 -2471.0022  32185.9985  
2016-01-08  607.049988        1   -89.0014  32096.9971  
2016-01-11  617.739990        1  1069.0002  33165.9973  
2016-01-12  617.890015        1    15.0025  33180.9998  
2016-01-13  581.809998        1 -3608.0017  29572.9981  
2016-01-14  593.000000        1  1119.0002  30691.9983  
2016-01-15  570.179993        1 -2282.0007  28409.9976  
2016-01-19  574.479980        1   429.9987  28839.9963  
2016-01-20  571.770020        1  -270.9960  28569.0003  
2016-01-21  575.020020        1   325.0000  28894.0003  
2016-01-22  596.380005        1  2135.9985  31029.9988  
2016-01-25  596.530029        1    15.0024  31045.0012  
2016-01-26  601.250000        1   471.9971  31516.9983  

[1056 rows x 9 columns]

In [ ]:


In [ ]: